command to retreive token#124
Conversation
|
It would be nice to have an option to specify hostname or URL in the key command, and use the same command for all forges. Some password databases come organized like that. |
Can you give an example of what you want ? For the moment : |
|
eg. in here You can see subprocess.check_output(['secret-tool', 'lookup', 'service', host, 'username', self.user]) That is same command used for every forge, and looked up by the 'service' tag. The 'host' argument is the host name of the forge. This is a database that is arbitrarily created like this by another tool, the secret-tool is generic tagged storage that does not itself interpret the tags in any way. |
|
Your solution still makes it possible (hopefully) to manually configure for each forge something like token = !secret-tool lookup service github.com type token or somesuch but does not provide the option to configure such command globally replacing the 'github.com' with the hostname of the forge. |
|
the command has now the environment so your example can be written as: token = !secret-tool lookup service $FORGE_DOMAIN type token |
andrew
left a comment
There was a problem hiding this comment.
Thanks for picking up #67. The ! prefix matches what people know from git's credential.helper, and the allowTokens=false guard correctly stops a checked-in .forge from running arbitrary commands, which is the obvious trap here. Good that there's an explicit test for it.
The main thing blocking this is the execution model: token commands run eagerly at config parse time, for every domain, on every invocation. See inline comments. Making resolution lazy (store the raw value, exec only when that domain's token is actually needed) fixes most of the issues at once.
Small thing: "retreive" → "retrieve" in the PR title.
| if strings.HasPrefix(v, "!") { | ||
| resolved, err := execValue(v[1:], name) | ||
| if err != nil { | ||
| return fmt.Errorf("%s: [%s] token command: %w", path, name, err) | ||
| } | ||
| ds.Token = resolved | ||
| ds.TokenExec = v | ||
| } else { | ||
| ds.Token = v | ||
| } |
There was a problem hiding this comment.
This runs at parse time, and config.Load() is called from rootCmd.PersistentPreRun (internal/cli/root.go:30) to read the default output format. So every forge invocation, including forge --help and forge auth login, executes the token command for every configured domain, not just the one being used.
With pass (GPG pinentry) or rbw (vault unlock) that means being challenged for unrelated domains on every command.
It also means a single failing command (vault locked, binary missing, typo) makes loadFile return an error, which propagates through resolve.go:196/276/290 and breaks every forge operation regardless of which host you're targeting.
I'd store the raw !cmd here and resolve lazily, e.g. a (*DomainSection).ResolveToken() called from resolve.TokenForDomain only when that domain is actually in use.
| c := exec.Command("sh", "-c", cmd) | ||
| c.Env = append(os.Environ(), "FORGE_DOMAIN="+domain) | ||
| out, err := c.Output() | ||
| if err != nil { | ||
| return "", fmt.Errorf("%q: %w", cmd, err) | ||
| } | ||
| return strings.TrimSpace(string(out)), nil |
There was a problem hiding this comment.
Two things here:
-
sh -cassumesshis in PATH. CI passes onwindows-latestbecause GitHub runners ship Git Bash, but a stock Windows install won't have it. The codebase already branches onruntime.GOOS == "windows"elsewhere; either fall back tocmd /Cor document this as Unix-only. -
c.Output()captures stderr into*exec.ExitError.Stderrbut the%wwrapping below doesn't surface it, so the user just sees"rbw get foo": exit status 1with no detail. Worth extracting and including in the error. Also consider wiringc.Stdin = os.Stdin/c.Stderr = os.Stderrso password managers that prompt on the terminal (pinentry-tty,rbw unlock) can actually do so.
| func readRawToken(fd int, oldState *term.State, r io.Reader) (string, error) { | ||
| const ( | ||
| ctrlC = 0x03 | ||
| ctrlD = 0x04 | ||
| enter = 0x0D | ||
| newline = 0x0A | ||
| backspace = 0x7F | ||
| del = 0x08 | ||
| printable = 0x20 | ||
| ) | ||
| defer func() { | ||
| _ = term.Restore(fd, oldState) | ||
| _, _ = fmt.Fprintln(os.Stderr) | ||
| }() | ||
|
|
||
| var buf []byte | ||
| b := make([]byte, 1) | ||
| for { | ||
| if _, err := r.Read(b); err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| switch b[0] { | ||
| case ctrlC, ctrlD: | ||
| return "", fmt.Errorf("interrupted") | ||
| case enter, newline: | ||
| return strings.TrimSpace(string(buf)), nil | ||
| case backspace, del: | ||
| if len(buf) > 0 { | ||
| buf = buf[:len(buf)-1] | ||
| } | ||
| default: | ||
| if b[0] >= printable { | ||
| buf = append(buf, b[0]) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
This reimplements password input byte-by-byte to support the Ctrl+E peek, but it's a regression from term.ReadPassword: arrow keys send \x1b[D etc., the 0x1b is filtered (< 0x20) but [ and D are appended to the token. term.ReadPassword also handled Ctrl+U / Ctrl+W.
A --token-cmd 'rbw get x' flag would be simpler, scriptable, and avoid the raw-mode code entirely. The Ctrl+E shortcut can stay as sugar if you like, but I'd lead with the flag in the README.
| // Returns the command prefixed with "!" for storage in the config. | ||
| func readCommandInteractive(domain string) (string, error) { | ||
| _, _ = fmt.Fprintf(os.Stderr, "Command for token (e.g. rbw get %s): ", domain) | ||
| line, _ := bufio.NewReader(os.Stdin).ReadString('\n') |
There was a problem hiding this comment.
Nit: error from ReadString is discarded.
| if cfgSection.TokenExec != "" { | ||
| sources = append(sources, fmt.Sprintf("config (cmd: %s)", cfgSection.TokenExec)) |
There was a problem hiding this comment.
TokenExec stores the raw value including the leading !, so this prints config (cmd: !echo secret). The ! is config syntax, not part of the command; either strip it for display or store v[1:] in TokenExec.
at resolution time, not at config load
closes #67
Instead of storing a token in plain text, a config entry can now reference a shell command prefixed with !. The command is executed at runtime and its stdout is used as the token, enabling integration with password managers such as
rbworpass.forge auth logingains a Ctrl+E shortcut at the token prompt to enter a command interactively.forge auth statusdisplays the command.